A Análise de Cluster (ou Agrupamento) é uma técnica de aprendizado não supervisionado que visa agrupar objetos semelhantes em grupos chamados clusters. O objetivo principal é maximizar a homogeneidade dentro dos grupos e a heterogeneidade entre os grupos. Na área da saúde, esta técnica tem diversas aplicações, como:
Neste tutorial, aplicaremos a Análise de Cluster em um conjunto de dados relacionado à saúde para identificar perfis de pacientes com diabetes.
# Carregamento dos pacotes necessários
library(tidyverse) # Manipulação de dados
library(cluster) # Algoritmos de clustering
library(factoextra) # Visualização de clusters
library(NbClust) # Determinação do número ideal de clusters
library(corrplot) # Visualização de correlações
library(gridExtra) # Organização de gráficosUtilizaremos o conjunto de dados “Pima Indians Diabetes”, que contém informações de 768 mulheres da população indígena Pima, com fatores de risco para diabetes. As variáveis incluem:
# Carregamento dos dados
url <- "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.csv"
diabetes <- read.csv(url, header = FALSE)
# Definição dos nomes das colunas
colnames(diabetes) <- c("Pregnancies", "Glucose", "BloodPressure",
"SkinThickness", "Insulin", "BMI",
"DiabetesPedigreeFunction", "Age", "Outcome")
# Visualização das primeiras linhas
head(diabetes)## Pregnancies Glucose BloodPressure SkinThickness Insulin BMI
## 1 6 148 72 35 0 33.6
## 2 1 85 66 29 0 26.6
## 3 8 183 64 0 0 23.3
## 4 1 89 66 23 94 28.1
## 5 0 137 40 35 168 43.1
## 6 5 116 74 0 0 25.6
## DiabetesPedigreeFunction Age Outcome
## 1 0.627 50 1
## 2 0.351 31 0
## 3 0.672 32 1
## 4 0.167 21 0
## 5 2.288 33 1
## 6 0.201 30 0
## 'data.frame': 768 obs. of 9 variables:
## $ Pregnancies : int 6 1 8 1 0 5 3 10 2 8 ...
## $ Glucose : int 148 85 183 89 137 116 78 115 197 125 ...
## $ BloodPressure : int 72 66 64 66 40 74 50 0 70 96 ...
## $ SkinThickness : int 35 29 0 23 35 0 32 0 45 0 ...
## $ Insulin : int 0 0 0 94 168 0 88 0 543 0 ...
## $ BMI : num 33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 0 ...
## $ DiabetesPedigreeFunction: num 0.627 0.351 0.672 0.167 2.288 ...
## $ Age : int 50 31 32 21 33 30 26 29 53 54 ...
## $ Outcome : int 1 0 1 0 1 0 1 0 1 1 ...
Antes de aplicar as técnicas de clustering, precisamos realizar algumas etapas de pré-processamento:
# Verificando valores ausentes (zeros em algumas variáveis são biologicamente impossíveis)
diabetes_clean <- diabetes %>%
mutate(
Glucose = ifelse(Glucose == 0, NA, Glucose),
BloodPressure = ifelse(BloodPressure == 0, NA, BloodPressure),
SkinThickness = ifelse(SkinThickness == 0, NA, SkinThickness),
Insulin = ifelse(Insulin == 0, NA, Insulin),
BMI = ifelse(BMI == 0, NA, BMI)
)
# Resumo estatístico
summary(diabetes_clean)## Pregnancies Glucose BloodPressure SkinThickness
## Min. : 0.000 Min. : 44.0 Min. : 24.00 Min. : 7.00
## 1st Qu.: 1.000 1st Qu.: 99.0 1st Qu.: 64.00 1st Qu.:22.00
## Median : 3.000 Median :117.0 Median : 72.00 Median :29.00
## Mean : 3.845 Mean :121.7 Mean : 72.41 Mean :29.15
## 3rd Qu.: 6.000 3rd Qu.:141.0 3rd Qu.: 80.00 3rd Qu.:36.00
## Max. :17.000 Max. :199.0 Max. :122.00 Max. :99.00
## NA's :5 NA's :35 NA's :227
## Insulin BMI DiabetesPedigreeFunction Age
## Min. : 14.00 Min. :18.20 Min. :0.0780 Min. :21.00
## 1st Qu.: 76.25 1st Qu.:27.50 1st Qu.:0.2437 1st Qu.:24.00
## Median :125.00 Median :32.30 Median :0.3725 Median :29.00
## Mean :155.55 Mean :32.46 Mean :0.4719 Mean :33.24
## 3rd Qu.:190.00 3rd Qu.:36.60 3rd Qu.:0.6262 3rd Qu.:41.00
## Max. :846.00 Max. :67.10 Max. :2.4200 Max. :81.00
## NA's :374 NA's :11
## Outcome
## Min. :0.000
## 1st Qu.:0.000
## Median :0.000
## Mean :0.349
## 3rd Qu.:1.000
## Max. :1.000
##
# Imputação de valores ausentes pela mediana (abordagem simples)
diabetes_imputed <- diabetes_clean %>%
mutate(
Glucose = ifelse(is.na(Glucose), median(Glucose, na.rm = TRUE), Glucose),
BloodPressure = ifelse(is.na(BloodPressure), median(BloodPressure, na.rm = TRUE), BloodPressure),
SkinThickness = ifelse(is.na(SkinThickness), median(SkinThickness, na.rm = TRUE), SkinThickness),
Insulin = ifelse(is.na(Insulin), median(Insulin, na.rm = TRUE), Insulin),
BMI = ifelse(is.na(BMI), median(BMI, na.rm = TRUE), BMI)
)
# Verificando após imputação
summary(diabetes_imputed)## Pregnancies Glucose BloodPressure SkinThickness
## Min. : 0.000 Min. : 44.00 Min. : 24.00 Min. : 7.00
## 1st Qu.: 1.000 1st Qu.: 99.75 1st Qu.: 64.00 1st Qu.:25.00
## Median : 3.000 Median :117.00 Median : 72.00 Median :29.00
## Mean : 3.845 Mean :121.66 Mean : 72.39 Mean :29.11
## 3rd Qu.: 6.000 3rd Qu.:140.25 3rd Qu.: 80.00 3rd Qu.:32.00
## Max. :17.000 Max. :199.00 Max. :122.00 Max. :99.00
## Insulin BMI DiabetesPedigreeFunction Age
## Min. : 14.0 Min. :18.20 Min. :0.0780 Min. :21.00
## 1st Qu.:121.5 1st Qu.:27.50 1st Qu.:0.2437 1st Qu.:24.00
## Median :125.0 Median :32.30 Median :0.3725 Median :29.00
## Mean :140.7 Mean :32.46 Mean :0.4719 Mean :33.24
## 3rd Qu.:127.2 3rd Qu.:36.60 3rd Qu.:0.6262 3rd Qu.:41.00
## Max. :846.0 Max. :67.10 Max. :2.4200 Max. :81.00
## Outcome
## Min. :0.000
## 1st Qu.:0.000
## Median :0.000
## Mean :0.349
## 3rd Qu.:1.000
## Max. :1.000
# Matriz de correlação
cor_matrix <- cor(diabetes_imputed[, 1:8])
corrplot(cor_matrix, method = "circle", type = "upper",
tl.col = "black", tl.srt = 45)# Distribuição das variáveis
p1 <- ggplot(diabetes_imputed, aes(x = Glucose)) +
geom_histogram(fill = "steelblue", bins = 30) +
theme_minimal() + labs(title = "Distribuição da Glucose")
p2 <- ggplot(diabetes_imputed, aes(x = BMI)) +
geom_histogram(fill = "steelblue", bins = 30) +
theme_minimal() + labs(title = "Distribuição do IMC")
p3 <- ggplot(diabetes_imputed, aes(x = Age)) +
geom_histogram(fill = "steelblue", bins = 30) +
theme_minimal() + labs(title = "Distribuição da Idade")
p4 <- ggplot(diabetes_imputed, aes(x = Insulin)) +
geom_histogram(fill = "steelblue", bins = 30) +
theme_minimal() + labs(title = "Distribuição da Insulina")
grid.arrange(p1, p2, p3, p4, ncol = 2)# Relação entre variáveis por status de diabetes
ggplot(diabetes_imputed, aes(x = BMI, y = Glucose, color = factor(Outcome))) +
geom_point(alpha = 0.6) +
scale_color_manual(values = c("blue", "red"),
labels = c("Sem Diabetes", "Com Diabetes"),
name = "Diagnóstico") +
theme_minimal() +
labs(title = "Relação entre IMC e Glucose por status de diabetes")
# Matriz de Correlação
Vamos selecionar as variáveis relevantes, excluir a variável alvo (Outcome) e normalizar os dados:
# Selecionando variáveis para clustering
diabetes_cluster <- diabetes_imputed %>%
select(-Outcome) # Removendo a variável alvo para não influenciar o clustering
# Normalização dos dados (importante para algoritmos baseados em distância)
diabetes_scaled <- scale(diabetes_cluster)
summary(diabetes_scaled)## Pregnancies Glucose BloodPressure SkinThickness
## Min. :-1.1411 Min. :-2.5513 Min. :-4.00001 Min. :-2.51479
## 1st Qu.:-0.8443 1st Qu.:-0.7197 1st Qu.:-0.69331 1st Qu.:-0.46729
## Median :-0.2508 Median :-0.1530 Median :-0.03197 Median :-0.01229
## Mean : 0.0000 Mean : 0.0000 Mean : 0.00000 Mean : 0.00000
## 3rd Qu.: 0.6395 3rd Qu.: 0.6109 3rd Qu.: 0.62937 3rd Qu.: 0.32896
## Max. : 3.9040 Max. : 2.5410 Max. : 4.10141 Max. : 7.95020
## Insulin BMI DiabetesPedigreeFunction
## Min. :-1.4664 Min. :-2.07343 Min. :-1.1888
## 1st Qu.:-0.2219 1st Qu.:-0.72074 1st Qu.:-0.6885
## Median :-0.1814 Median :-0.02258 Median :-0.2999
## Mean : 0.0000 Mean : 0.00000 Mean : 0.0000
## 3rd Qu.:-0.1554 3rd Qu.: 0.60286 3rd Qu.: 0.4659
## Max. : 8.1651 Max. : 5.03911 Max. : 5.8797
## Age
## Min. :-1.0409
## 1st Qu.:-0.7858
## Median :-0.3606
## Mean : 0.0000
## 3rd Qu.: 0.6598
## Max. : 4.0611
# Método do cotovelo
fviz_nbclust(diabetes_scaled, kmeans, method = "wss") +
geom_vline(xintercept = 3, linetype = 2) +
labs(title = "Método do Cotovelo")# Método da silhueta
fviz_nbclust(diabetes_scaled, kmeans, method = "silhouette") +
labs(title = "Método da Silhueta")# Abordagem estatística (Gap statistic)
set.seed(123)
gap_stat <- clusGap(diabetes_scaled, FUN = kmeans, nstart = 25,
K.max = 10, B = 50)
fviz_gap_stat(gap_stat) +
labs(title = "Estatística Gap")# Cálculo da matriz de distância
dist_matrix <- dist(diabetes_scaled, method = "euclidean")
# Clustering hierárquico
hc <- hclust(dist_matrix, method = "ward.D2")
# Visualização do dendrograma
fviz_dend(hc, k = 3, # Corte para 3 clusters
cex = 0.5,
palette = "jco",
rect = TRUE,
rect_fill = TRUE,
rect_border = "jco",
labels_track_height = 0) +
labs(title = "Dendrograma do Clustering Hierárquico")O dendrograma mostra:
# Aplicando K-means com k=3
set.seed(123)
km <- kmeans(diabetes_scaled, centers = 3, nstart = 25)
# Visualização dos clusters
fviz_cluster(list(data = diabetes_scaled, cluster = km$cluster),
palette = c("#00AFBB", "#FC4E07", "#E7B800"),
ellipse.type = "convex",
repel = TRUE,
ggtheme = theme_minimal()) +
labs(title = "Clusters pelo método K-means")# Adicionando a informação de cluster ao dataset original
diabetes_results <- diabetes_imputed %>%
mutate(cluster = factor(km$cluster),
diabetes_status = factor(Outcome, labels = c("Sem Diabetes", "Com Diabetes")))O gráfico de dispersão bidimensional mostra:
# Estatísticas descritivas por cluster
cluster_summary <- diabetes_results %>%
group_by(cluster) %>%
summarise(
n = n(),
Idade_media = mean(Age),
IMC_medio = mean(BMI),
Glucose_media = mean(Glucose),
Pressao_media = mean(BloodPressure),
Insulina_media = mean(Insulin),
Perc_diabetes = mean(Outcome) * 100
)
# Visualização das estatísticas
knitr::kable(cluster_summary, digits = 2,
caption = "Características médias por cluster")| cluster | n | Idade_media | IMC_medio | Glucose_media | Pressao_media | Insulina_media | Perc_diabetes |
|---|---|---|---|---|---|---|---|
| 1 | 337 | 25.94 | 28.56 | 106.53 | 66.75 | 113.33 | 13.06 |
| 2 | 248 | 46.04 | 32.81 | 130.79 | 78.42 | 139.63 | 51.61 |
| 3 | 183 | 29.33 | 39.16 | 137.14 | 74.60 | 192.44 | 52.46 |
# Distribuição de diabetes por cluster
ggplot(diabetes_results, aes(x = cluster, fill = diabetes_status)) +
geom_bar(position = "fill") +
scale_fill_manual(values = c("blue", "red")) +
theme_minimal() +
labs(title = "Proporção de casos de diabetes por cluster",
y = "Proporção", x = "Cluster")# Visualização das variáveis por cluster
p1 <- ggplot(diabetes_results, aes(x = cluster, y = Age, fill = cluster)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Idade por cluster")
p2 <- ggplot(diabetes_results, aes(x = cluster, y = Glucose, fill = cluster)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Glucose por cluster")
p3 <- ggplot(diabetes_results, aes(x = cluster, y = BMI, fill = cluster)) +
geom_boxplot() +
theme_minimal() +
labs(title = "IMC por cluster")
p4 <- ggplot(diabetes_results, aes(x = cluster, y = Insulin, fill = cluster)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Insulina por cluster")
grid.arrange(p1, p2, p3, p4, ncol = 2)Idade: O cluster 2 (verde) tem idade média significativamente maior (cerca de 46 anos) Glucose: Os clusters 2 e 3 têm níveis médios de glicose mais elevados IMC: O cluster 3 (azul) apresenta IMC claramente mais alto (média ~40) Insulina: O cluster 3 também tem níveis de insulina mais elevados
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.5110 1.2229 1.0684 0.9574 0.87685 0.73707 0.68443
## Proportion of Variance 0.2854 0.1870 0.1427 0.1146 0.09611 0.06791 0.05856
## Cumulative Proportion 0.2854 0.4723 0.6150 0.7296 0.82568 0.89359 0.95215
## PC8
## Standard deviation 0.61872
## Proportion of Variance 0.04785
## Cumulative Proportion 1.00000
# Visualização dos clusters no espaço PCA
fviz_pca_ind(pca_result,
geom.ind = "point",
col.ind = diabetes_results$cluster,
palette = c("#00AFBB", "#FC4E07", "#E7B800"),
addEllipses = TRUE,
legend.title = "Cluster") +
labs(title = "Clusters no espaço de componentes principais")# Contribuição das variáveis para os componentes principais
fviz_pca_var(pca_result,
col.var = "contrib",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
repel = TRUE) +
labs(title = "Contribuição das variáveis para os componentes principais")Os três clusters em um espaço bidimensional após redução de dimensionalidade por PCA Elipses de confiança para cada cluster, mostrando o grau de dispersão Separação razoável entre clusters, mas com áreas de sobreposição
As variáveis Age e Pregnancies têm maior contribuição para a dimensão vertical (PC2) Glucose, SkinThickness, Insulin e BMI contribuem mais para a dimensão horizontal (PC1) DiabetesPedigreeFunction tem contribuição significativa para ambas dimensões Isto ajuda a entender quais variáveis são mais importantes na formação dos clusters
Com base nas análises realizadas, podemos caracterizar os três clusters da seguinte forma:
# Criando rótulos descritivos para os clusters
cluster_labels <- c(
"Cluster 1" = "Baixo Risco",
"Cluster 2" = "Risco Intermediário",
"Cluster 3" = "Alto Risco"
)
# Adicionando os rótulos ao dataset
diabetes_results <- diabetes_results %>%
mutate(cluster_desc = recode(cluster, !!!cluster_labels))
# Tabela com perfil detalhado dos clusters
cluster_profile <- diabetes_results %>%
group_by(cluster_desc) %>%
summarise(
Tamanho = n(),
Percentual = n()/nrow(diabetes_results) * 100,
Idade_media = mean(Age),
IMC_medio = mean(BMI),
Glucose_media = mean(Glucose),
Insulina_media = mean(Insulin),
Pressao_media = mean(BloodPressure),
Historico_familiar = mean(DiabetesPedigreeFunction),
Incidencia_diabetes = mean(Outcome) * 100
)
knitr::kable(cluster_profile, digits = 1,
caption = "Perfil detalhado dos clusters identificados")| cluster_desc | Tamanho | Percentual | Idade_media | IMC_medio | Glucose_media | Insulina_media | Pressao_media | Historico_familiar | Incidencia_diabetes |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 337 | 43.9 | 25.9 | 28.6 | 106.5 | 113.3 | 66.8 | 0.4 | 13.1 |
| 2 | 248 | 32.3 | 46.0 | 32.8 | 130.8 | 139.6 | 78.4 | 0.5 | 51.6 |
| 3 | 183 | 23.8 | 29.3 | 39.2 | 137.1 | 192.4 | 74.6 | 0.6 | 52.5 |
Cluster 1 - Baixo Risco: Este grupo é caracterizado por pacientes mais jovens, com níveis normais de glicose, IMC mais baixo e menor incidência de diabetes. Representa indivíduos com menor risco metabólico e menor necessidade de intervenções imediatas.
Cluster 2 - Risco Intermediário: Este grupo inclui pacientes com idade intermediária, níveis de glicose e IMC moderadamente elevados. A incidência de diabetes é maior que no Cluster 1, sugerindo um grupo que requer monitoramento regular e intervenções preventivas.
Cluster 3 - Alto Risco: Este grupo é composto principalmente por pacientes mais velhos, com níveis elevados de glicose, IMC mais alto e maior incidência de diabetes. Representa um grupo prioritário para intervenções clínicas intensivas.
A identificação destes clusters tem várias implicações para a gestão em saúde:
Estratificação de risco: Os clusters permitem categorizar pacientes em diferentes níveis de risco, orientando a alocação de recursos de saúde.
Personalização de intervenções:
Otimização de recursos: Direcionamento de recursos mais intensivos para pacientes do Cluster 3, enquanto abordagens mais gerais podem ser suficientes para o Cluster 1.
Criação de protocolos clínicos: Desenvolvimento de protocolos específicos para cada cluster, considerando suas características particulares.
Predição de resultados: Uso dos perfis de cluster para estimar prognósticos e planejar intervenções preventivas.
# Visualização da distribuição dos clusters com tamanho proporcional ao número de casos
ggplot(diabetes_results, aes(x = cluster_desc, fill = diabetes_status)) +
geom_bar() +
scale_fill_manual(values = c("blue", "red")) +
theme_minimal() +
labs(title = "Distribuição de casos de diabetes por cluster",
x = "Perfil do Cluster", y = "Número de pacientes",
fill = "Status de Diabetes")O cluster 1 tem a melhor silhueta média (0.32), indicando boa coesão Os clusters 2 e 3 têm valores mais baixos (0.15 e 0.04), sugerindo maior heterogeneidade A linha tracejada horizontal indica a silhueta média global (~0.22)
# Tabela de contingência entre clusters e status de diabetes
contingency_table <- table(Cluster = diabetes_results$cluster,
Diabetes = diabetes_results$diabetes_status)
knitr::kable(contingency_table,
caption = "Distribuição de casos de diabetes por cluster")| Sem Diabetes | Com Diabetes |
|---|---|
| 293 | 44 |
| 120 | 128 |
| 87 | 96 |
##
## Pearson's Chi-squared test
##
## data: contingency_table
## X-squared = 126.1, df = 2, p-value < 2.2e-16
Pacientes jovens (média 25,9 anos) IMC normal a levemente elevado (28,6) Glicose média mais baixa (106,5) Apenas 13% têm diabetes Pressão arterial média mais baixa (66,8)
Pacientes mais velhos (média 46 anos) IMC moderadamente elevado (32,8) Níveis de glicose elevados (130,8) 51,6% têm diabetes Pressão arterial mais alta (78,4)
Idade intermediária (média 29,3 anos) IMC muito elevado (39,2) - obesidade significativa Níveis de glicose mais elevados (137,1) 52,5% têm diabetes Níveis de insulina mais altos (192,4)
O teste qui-quadrado (χ² = 126.1, p < 0.0001) confirma que existe associação estatisticamente significativa entre os clusters identificados e o diagnóstico de diabetes, validando a relevância clínica dos grupos.
A análise de cluster aplicada a esta base de dados permitiu identificar três perfis distintos de pacientes, com diferentes níveis de risco para diabetes. Esta estratificação pode ser extremamente útil para a gestão em saúde, permitindo:
Otimização de recursos: Ao identificar grupos de alto risco, os recursos de saúde podem ser direcionados de forma mais eficiente.
Medicina personalizada: A partir da compreensão dos perfis, é possível desenvolver abordagens personalizadas para cada grupo.
Prevenção direcionada: Estratégias preventivas específicas podem ser implementadas para cada cluster.
Monitoramento inteligente: A identificação dos clusters permite estabelecer protocolos de acompanhamento com frequências diferentes para cada grupo.
As análises sugerem estratégias diferenciadas para cada grupo:
Foco em prevenção primária e educação em saúde Monitoramento menos frequente Intervenções de baixo custo focadas em manutenção da saúde
#Para o Cluster 2 (Risco Intermediário): Monitoramento regular Modificações no estilo de vida mais intensivas Possível intervenção farmacológica preventiva Atenção especial à idade mais avançada
Acompanhamento intensivo Tratamento agressivo dos fatores de risco, especialmente obesidade Rastreamento de complicações Intervenções nutricionais e metabólicas específicas
Esta estratificação permite direcionar recursos de forma mais eficiente e personalizar o cuidado, concentrando esforços mais intensivos nos grupos de maior risco. Os resultados também mostram que a idade elevada (Cluster 2) e a obesidade significativa (Cluster 3) representam dois caminhos diferentes para o desenvolvimento de diabetes, o que sugere abordagens preventivas e terapêuticas distintas para cada grupo. A análise demonstra o potencial da técnica de clustering para auxiliar decisões clínicas e de gestão em saúde, identificando padrões que poderiam não ser evidentes ao analisar cada variável isoladamente.
# Visualização final tridimensional dos clusters
plotly::plot_ly(
x = pca_result$x[,1],
y = pca_result$x[,2],
z = pca_result$x[,3],
type = "scatter3d",
mode = "markers",
color = factor(diabetes_results$cluster),
colors = c("#00AFBB", "#FC4E07", "#E7B800"),
marker = list(size = 5)
) %>%
plotly::layout(
title = "Visualização 3D dos clusters (PCA)",
scene = list(
xaxis = list(title = "PC1"),
yaxis = list(title = "PC2"),
zaxis = list(title = "PC3")
)
)Everitt, B. S., Landau, S., Leese, M., & Stahl, D. (2011). Cluster Analysis. Wiley.
Kaufman, L., & Rousseeuw, P. J. (2009). Finding Groups in Data: An Introduction to Cluster Analysis. Wiley.
Smith, J. W., Everhart, J. E., Dickson, W. C., Knowler, W. C., & Johannes, R. S. (1988). Using the ADAP learning algorithm to forecast the onset of diabetes mellitus. Proceedings of the Annual Symposium on Computer Application in Medical Care, 261-265.
Zhang, X., Gregg, E. W., Williamson, D. F., et al. (2010). A1C level and future risk of diabetes: a systematic review. Diabetes Care, 33(7), 1665-1673.
Hennig, C., Meila, M., Murtagh, F., & Rocci, R. (Eds.). (2015). Handbook of Cluster Analysis. CRC Press.